/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ArkClass KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkAssignStmt, ArkMethod, Local, Stmt, Value, ArkStaticFieldRef, ClassType, EnumValueType, ArkClass } from 'arkanalyzer/lib';
import { BaseChecker, BaseMetaData } from '../BaseChecker';
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { Rule, Defects, MethodMatcher, MatcherTypes, MatcherCallback, CheckerUtils } from '../../Index';
import { IssueReport } from '../../model/Defects';
import { NullConstant, NumberConstant, UndefinedConstant } from 'arkanalyzer/lib/core/base/Constant';

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'AVPlayBackStateCheck');

const gMetaData: BaseMetaData = {
    severity: 3,
    ruleDocPath: 'docs/av-playback-state-check.md',
    description: 'When state is PLAYBACK_STATE_PLAY, position should not be null or undefined. Please set a valid position value.'
};

// 字符串常量定义
const AV_PLAYBACK_STATE_CLASS = 'AVPlaybackState';
const PLAYBACK_STATE_PLAY = 'PLAYBACK_STATE_PLAY';
const STATE_FIELD = 'state';
const POSITION_FIELD = 'position';
const SET_AV_PLAYBACK_STATE_METHOD = 'setAVPlaybackState';

export class AVPlayBackStateCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gMetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];

    private methodMatcher: MethodMatcher = {
        matcherType: MatcherTypes.METHOD
    };

    public registerMatchers(): MatcherCallback[] {
        return [{
            matcher: this.methodMatcher,
            callback: this.check
        }];
    }

    public check = (targetMethod: ArkMethod): void => {
        const stmts = targetMethod.getBody()?.getCfg().getStmts() ?? [];

        for (let stmt of stmts) {
            this.processStatement(stmt);
        }
    };

    private processStatement(stmt: Stmt): void {
        const invoker = CheckerUtils.getInvokeExprFromStmt(stmt);
        if (!invoker || invoker.getMethodSignature().getMethodSubSignature().getMethodName() !== SET_AV_PLAYBACK_STATE_METHOD) {
            return;
        }

        const args = invoker.getArgs();
        if (args.length < 1) {
            return;
        }

        const playbackStateArg = args[0];
        this.processPlaybackStateArg(playbackStateArg);
    }

    private processPlaybackStateArg(playbackStateArg: Value): void {
        if (!(playbackStateArg instanceof Local)) {
            return;
        }

        const playbackStateArgClassType = playbackStateArg.getType();
        if (!(playbackStateArgClassType instanceof ClassType)) {
            return;
        }

        const playbackStateArgClassTypeName = playbackStateArgClassType.getClassSignature().getClassName();
        if (playbackStateArgClassTypeName !== AV_PLAYBACK_STATE_CLASS) {
            return;
        }

        const playbackStateArgStmt = playbackStateArg.getDeclaringStmt();
        if (!(playbackStateArgStmt instanceof ArkAssignStmt)) {
            return;
        }

        const aVPlaybackStateRightOp = playbackStateArgStmt.getRightOp();
        if (!(aVPlaybackStateRightOp instanceof Local)) {
            return;
        }

        const aVPlaybackStateRightOpClassType = aVPlaybackStateRightOp.getType();
        if (!(aVPlaybackStateRightOpClassType instanceof ClassType)) {
            return;
        }

        const aVPlaybackStateRightOpClassSignature = aVPlaybackStateRightOpClassType.getClassSignature();
        const currentFile = playbackStateArg.getDeclaringStmt()?.getCfg().getDeclaringMethod().getDeclaringArkFile();
        const currentClass = currentFile?.getClass(aVPlaybackStateRightOpClassSignature);

        if (!currentClass) {
            return;
        }

        this.processStateField(currentClass);
    }

    private processStateField(currentClass: ArkClass): void {
        const stateField = currentClass.getFieldWithName(STATE_FIELD);
        if (!stateField) {
            return;
        }

        const stateStmts = stateField.getInitializer();
        if (!stateStmts) {
            return;
        }

        for (const stateStmt of stateStmts) {
            this.processStateStatement(stateStmt, currentClass);
        }
    }

    private processStateStatement(stateStmt: Stmt, currentClass: ArkClass): void {
        if (!(stateStmt instanceof ArkAssignStmt)) {
            return;
        }

        const stateRightOp = stateStmt.getRightOp();

        if (stateRightOp instanceof ArkStaticFieldRef) {
            this.processStaticFieldRef(stateRightOp, currentClass, stateStmt);
        } else if (stateRightOp instanceof NumberConstant) {
            this.processNumberConstant(stateRightOp, currentClass, stateStmt);
        }
    }

    private processStaticFieldRef(stateRightOp: ArkStaticFieldRef, currentClass: ArkClass, stateStmt: ArkAssignStmt): void {
        const stateValueField = stateRightOp.getFieldSignature();
        const stateValueType = stateValueField.getType();

        if (!(stateValueType instanceof EnumValueType)) {
            return;
        }

        const fieldName = stateValueType.getFieldSignature().getFieldName();
        if (fieldName !== PLAYBACK_STATE_PLAY) {
            return;
        }

        this.checkPositionField(currentClass, stateStmt, stateRightOp);
    }

    private processNumberConstant(stateRightOp: NumberConstant, currentClass: ArkClass, stateStmt: ArkAssignStmt): void {
        if (stateRightOp.getValue() !== '2') {
            return;
        }

        this.checkPositionField(currentClass, stateStmt, stateRightOp);
    }

    private checkPositionField(currentClass: ArkClass, stateStmt: ArkAssignStmt, stateRightOp: Value): void {
        const positionField = currentClass.getFieldWithName(POSITION_FIELD);

        if (!positionField) {
            this.addIssueReport(stateStmt, stateRightOp);
            return;
        }

        const positionStmt = positionField.getInitializer()[0];
        if (!(positionStmt instanceof ArkAssignStmt)) {
            return;
        }

        const positionRightOp = positionStmt.getRightOp();
        if (positionRightOp instanceof NullConstant || positionRightOp instanceof UndefinedConstant) {
            this.addIssueReport(positionStmt, positionRightOp);
        }
    }


    /**
     * 报告问题
     */
    private addIssueReport(stmt: Stmt, rightOp: Value): void {
        const reportPosition = stmt.getOperandOriginalPosition(rightOp)!;
        const filePath = stmt.getCfg().getDeclaringMethod().getDeclaringArkFile().getFilePath();
        const severity = this.rule.alert ?? this.metaData.severity;
        const line = reportPosition.getFirstLine();
        const startCol = reportPosition.getFirstCol();
        const endCol = reportPosition.getLastCol();
        let defects = new Defects(line, startCol, endCol, this.metaData.description, severity, this.rule.ruleId,
            filePath, this.metaData.ruleDocPath, true, false, true);
        this.issues.push(new IssueReport(defects, undefined));
    }
}

